using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace OhioState.AssetManaged
{
    /// <summary>
    /// The AssetManager is used to keep track of all registered assets in the system.
    /// </summary>
    /// <remarks>This class is a Singleton.</remarks>
    /// <remarks>This class (and the instance) can be accessed remotely.</remarks>
    public sealed class AssetManager : MarshalByRefObject
    {
        #region Public Events
        /// <summary>
        /// Event fired whenever an asset is added.
        /// </summary>
        [field: NonSerialized()]
        public event IAssetManagedHandler AssetAdded;
        /// <summary>
        /// Event fired whenever an asset is removed from the AssetManager.
        /// </summary>
        [field: NonSerialized()]
        public event IAssetManagedHandler AssetRemoved;
        #endregion

        #region Public Properties
        /// <summary>
        /// Get the single instance of the AssetManager.
        /// </summary>
        public static AssetManager Instance
        {
            get
            {
                return instance;
            }

            //set //we'll be assigning this when doing remoting
            //{
            //    instance = value;
            //}
        }

        /// <summary>
        /// Get the number of assets currently being managed.
        /// </summary>
        public int Count
        {
            get { return assets.Count; }
        }
        #endregion

        #region Add and Remove Assets
        /// <summary>
        /// Add an asset to the AssetManager.
        /// </summary>
        /// <param name="asset">An asset support the <typeparamref name="IAssetManaged"/> interface.</param>
        /// <returns>Returns null if the asset is already being managed. Otherwise it returns the asset that was passed in.</returns>
        /// <remarks>The asset may have its name changed to avoid name conflicts.</remarks>
        public IAssetManaged Add(IAssetManaged asset)
        {
            // check for duplicates
            if (!assets.Contains(asset))
            {
                // Rename the asset if its type and name conflict.
                // TODO: This will fail to rename objects that have the same interface but different types.
                IAssetManaged existingAsset = Find(asset.GetType(), asset.Name);
                String name = asset.Name;
                int n = 2;
                while (existingAsset != null)
                {
                    asset.SetName(name + " (" + (n++).ToString() + ")");
                    existingAsset = Find(asset.GetType(), asset.Name);
                }

                // Add the asset to the internal collection
                assets.Add(asset);

                // Let subscribers know a new asset was added.
                NewAssetAdded(asset);

                // Asset added successfully, so return this asset.
                return asset;
            }
            // Asset is already in the system, so return null.
            return null;
        }

        /// <summary>
        /// Removes the asset from the <typeparamref name="AssetManager"/>.
        /// </summary>
        /// <param name="asset">The asset to remove.</param>
        /// <returns><c>true</c> if the asset is successfully removed; otherwise, <c>false</c>. 
        /// This method also returns <c>false</c> if item was not found in 
        /// the <typeparamref name="AssetManager"/>.</returns>
        public bool Remove(IAssetManaged asset)
        {
            if (assets.Remove(asset))
            {
                _eventsHelper.Fire(AssetRemoved, asset);
                return true;
            }
            return false;
        }
        #endregion

        public void RemoveAll()
        {
            assets.Clear();
        }

        #region Query or get items
        /// <summary>
        /// Get a list of assets that are assignable to the desired type.
        /// </summary>
        /// <param name="type">The <typeparamref name="System.Type"/> to search for.</param>
        /// <returns>A <typeparamref name="List{IAssetManaged}"/> that supports the desired type.</returns>
        public List<IAssetManaged> FindAll(Type type)
        {
            List<IAssetManaged> list = new List<IAssetManaged>();
            foreach (IAssetManaged asset in assets)
            {
                if (type.IsAssignableFrom(asset.GetType()))
                {
                    list.Add(asset);
                }
            }
            return list;
        }
        /// <summary>
        /// Get an asset according to its type and name.
        /// </summary>
        /// <param name="type">The <typeparamref name="System.Type"/> of the desired asset.</param>
        /// <param name="name"></param>
        /// <returns>The <typeparamref name="IAssetManaged"/> asset if it was found. Otherwise <c>null</c>.</returns>
        public IAssetManaged Find(Type type, String name)
        {
            foreach (IAssetManaged asset in assets)
            {
                if (type.IsAssignableFrom(asset.GetType()) && (name == asset.Name))
                {
                    return asset;
                }
            }
            return null;
        }

        /// <summary>
        /// Determines whether an asset is in the <typeparamref name="AssetManager"/>.
        /// </summary>
        /// <param name="asset">The asset to locate in the <typeparamref name="AssetManager"/></param>
        /// <returns><c>true</c> if the asset is found in the <typeparamref name="AssetManager"/>; otherwise, <c>false</c>.</returns>
        public bool Contains(IAssetManaged asset)
        {
            return assets.Contains(asset);
        }
        #endregion

        #region I/O
        /// <summary>
        /// Imports assets from a file into the <typeparamref name="AssetManager"/>.
        /// </summary>
        /// <param name="filename">A string that contains the name of the file from which to import assets.</param>
        public void LoadFromFile(String filename)
        {
            using (Stream stream = File.Open(filename, FileMode.Open))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Context = new StreamingContext(StreamingContextStates.Clone);
                formatter.FilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
                stream.Position = 0;
                
                List<IAssetManaged> list = (List<IAssetManaged>)formatter.Deserialize(stream);
                //
                // Loop thru the list once to add any non-standard items
                //    This allows already defined objects to override the objects in the
                //    stream. This may or may not be what a user wants / expects.
                //
                List<IAssetManaged> newAssets = new List<IAssetManaged>();
                foreach (IAssetManaged item in list)
                {
                    IAssetManaged asset = Find(item.GetType(), item.Name);
                    //
                    // If the item is not already in the AssetManager, then
                    // add it.
                    if (asset == null)
                    {
                        this.Add(item);
                        newAssets.Add(item);
                    }
                }
                //
                // Loop thru a second time having each object reset its data from the AssetManager.
                // This is needed to ensure that events are re-wired correctly.
                //
                foreach (IAssetManaged asset in newAssets)
                {
                    asset.ReaquireAssets();
                }
            }
        }

        /// <summary>
        /// Exports all assets in the <typeparamref name="AssetManager"/>to a file.
        /// </summary>
        /// <param name="filename">A string that contains the name of the file in which to save the assets.</param>
        /// <remarks>The specified file will be overwritten.</remarks>
        public void SaveToFile(String filename)
        {
            Stream stream = File.Open(filename, FileMode.Create);
            BinaryFormatter formatter = new BinaryFormatter();
            //formatter.Context = new StreamingContext(StreamingContextStates.Persistence);
            //BinaryFormatter formatter = new BinaryFormatter(
            //    new System.Runtime.Remoting.Messaging.RemotingSurrogateSelector(),
            //    new StreamingContext(StreamingContextStates.Persistence));//File));//File seems to be working fine in the same process

            using (stream)
            {
                formatter.Serialize(stream, assets);
            }
        }
        #endregion

        #region Private Implementation
        //
        // Constructors
        //
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static AssetManager()
        {
        }

        private AssetManager()
        {
            assets = new List<IAssetManaged>();

            //_eventsHelper = new EventsStrategyOneWay();
            _eventsHelper = new EventsStrategySerial();
        }


        private void NewAssetAdded(IAssetManaged asset)
        {
            if (AssetAdded != null)
            {
                try
                {
                    _eventsHelper.Fire(AssetAdded, asset);
                }
                catch { }
            }
        }
        #endregion

        #region Member variables
        //
        // Event helper for firing events
        //
        [NonSerialized()]
        private EventsStrategyAbstract _eventsHelper;

        //
        // Singleton design pattern
        //
        [NonSerialized()]
        private static AssetManager instance = new AssetManager();

        //
        // A list of all the assets the manager is tracking
        //  This has been changed to a dictionary of IAssetManaged objects
        //  to their containers because it makes getting the container information
        //  when the object is known much quicker, but doesn't not hurt the performance
        //  of other operations.
        //
        [NonSerialized()]
        private List<IAssetManaged> assets;
        //private Dictionary<IAssetManaged, AssetContainer> assets;
        #endregion
    }
}